function WebGLAttributes(gl, capabilities) {

  const isWebGL2 = capabilities.isWebGL2;

  const buffers = new WeakMap();

  function createBuffer(attribute, bufferType) {

    const array = attribute.array;
    const usage = attribute.usage;

    const buffer = gl.createBuffer();

    gl.bindBuffer(bufferType, buffer);
    gl.bufferData(bufferType, array, usage);

    attribute.onUploadCallback();

    let type = gl.FLOAT;

    if (array instanceof Float32Array) {

      type = gl.FLOAT;

    } else if (array instanceof Float64Array) {

      console.warn('THREE.WebGLAttributes: Unsupported data buffer format: Float64Array.');

    } else if (array instanceof Uint16Array) {

      type = gl.UNSIGNED_SHORT;

    } else if (array instanceof Int16Array) {

      type = gl.SHORT;

    } else if (array instanceof Uint32Array) {

      type = gl.UNSIGNED_INT;

    } else if (array instanceof Int32Array) {

      type = gl.INT;

    } else if (array instanceof Int8Array) {

      type = gl.BYTE;

    } else if (array instanceof Uint8Array) {

      type = gl.UNSIGNED_BYTE;

    }

    return {
      buffer: buffer,
      type: type,
      bytesPerElement: array.BYTES_PER_ELEMENT,
      version: attribute.version
    };

  }

  function updateBuffer(buffer, attribute, bufferType) {

    const array = attribute.array;
    const updateRange = attribute.updateRange;

    gl.bindBuffer(bufferType, buffer);

    if (updateRange.count === -1) {

      // Not using update ranges

      gl.bufferSubData(bufferType, 0, array);

    } else {

      if (isWebGL2) {

        gl.bufferSubData(bufferType, updateRange.offset * array.BYTES_PER_ELEMENT,
          array, updateRange.offset, updateRange.count);

      } else {

        gl.bufferSubData(bufferType, updateRange.offset * array.BYTES_PER_ELEMENT,
          array.subarray(updateRange.offset, updateRange.offset + updateRange.count));

      }

      updateRange.count = -1; // reset range

    }

  }

  //

  function get(attribute) {

    if (attribute.isInterleavedBufferAttribute) attribute = attribute.data;

    return buffers.get(attribute);

  }

  function remove(attribute) {

    if (attribute.isInterleavedBufferAttribute) attribute = attribute.data;

    const data = buffers.get(attribute);

    if (data) {

      gl.deleteBuffer(data.buffer);

      buffers.delete(attribute);

    }

  }

  function update(attribute, bufferType) {

    if (attribute.isGLBufferAttribute) {

      var cached = buffers.get(attribute);

      if (!cached || cached.version < attribute.version) {

        buffers.set(attribute, {
          buffer: attribute.buffer,
          type: attribute.type,
          bytesPerElement: attribute.elementSize,
          version: attribute.version
        });

      }

      return;

    }

    if (attribute.isInterleavedBufferAttribute) attribute = attribute.data;

    const data = buffers.get(attribute);

    if (data === undefined) {

      buffers.set(attribute, createBuffer(attribute, bufferType));

    } else if (data.version < attribute.version) {

      updateBuffer(data.buffer, attribute, bufferType);

      data.version = attribute.version;

    }

  }

  return {

    get: get,
    remove: remove,
    update: update

  };

}


export {WebGLAttributes};
